<!--
Tests that AR camera access returns non-empty GLTexture.
-->
<html>
  <head>
    <link rel="stylesheet" type="text/css" href="../resources/webxr_e2e.css">
  </head>
  <body>
    <canvas id="webgl-canvas"></canvas>
    <script src="../../../../../../third_party/blink/web_tests/resources/testharness.js"></script>
    <script src="../resources/webxr_e2e.js"></script>
    <script>var shouldAutoCreateNonImmersiveSession = false;</script>
    <script src="../resources/webxr_boilerplate.js"></script>
    <!-- Required features must be set after importing webxr_boilerplate.js to avoid overwrite -->
    <script>var immersiveArSessionInit = { requiredFeatures: ['camera-access'] };</script>
    <script>
      setup({single_test: true});

      // How many frames with viewer poses some tests will wait for until trying to
      // exercise the API and run the test case:
      const MIN_NUM_FRAMES_WITH_POSES = 10;
      // How many frames with viewer poses some tests will run the test case for:
      const MAX_NUM_FRAMES_WITH_POSES = 20;

      let numPosesFound = 0;
      let cameraImageTexture = null;
      let ranAtLeastOnce = false;
      let pixels = null;

      function stepStartStoringCameraTexture(numCalls) {
        const webglCanvas = document.getElementById('webgl-canvas');
        const gl = webglCanvas.getContext('webgl', {
                xrCompatible: true
            });
        const sessionInfo = sessionInfos[sessionTypes.AR];
        const referenceSpace = sessionInfo.currentRefSpace;
        const glBinding = new XRWebGLBinding(sessionInfo.currentSession, gl);

        onARFrameCallback = (session, frame) => {
          const pose = frame.getViewerPose(referenceSpace);
          if (pose) {
            numPosesFound++;

            // Wait several frames to allow the image buffer to be populated by a camera
            // texture before attempting to get the camera image texture.
            if (numPosesFound > MIN_NUM_FRAMES_WITH_POSES) {
              for (let view of pose.views) {
                if (view.camera) {
                  ranAtLeastOnce = true;
                  // Used to test that multiple consecutive calls will all return non-null textures.
                  for (let remainingCalls = numCalls; remainingCalls > 0; remainingCalls--) {
                    cameraImageTexture = glBinding.getCameraImage(view.camera);
                    assert_not_equals(cameraImageTexture, null,
                                      "XRWebGLBinding.getCameraImage(...) returned null texture.");
                  }
                }
              }
            }
          }

          if (numPosesFound > MIN_NUM_FRAMES_WITH_POSES) {
            onARFrameCallback = null;
            assert_true(ranAtLeastOnce);
            done();
          }
        };
      }

      function stepStartStoreAndDeleteCameraTexture() {
        const webglCanvas = document.getElementById('webgl-canvas');
        const gl = webglCanvas.getContext('webgl', {
                xrCompatible: true
            });
        const sessionInfo = sessionInfos[sessionTypes.AR];
        const referenceSpace = sessionInfo.currentRefSpace;
        const glBinding = new XRWebGLBinding(sessionInfo.currentSession, gl);

        onARFrameCallback = (session, frame) => {
          const pose = frame.getViewerPose(referenceSpace);
          if (pose) {
            numPosesFound++;

            for (let view of pose.views) {
              if (view.camera) {
                ranAtLeastOnce = true;
                cameraImageTexture = glBinding.getCameraImage(view.camera);
                gl.deleteTexture(cameraImageTexture);
              }
            }
          }

          if (numPosesFound > MIN_NUM_FRAMES_WITH_POSES) {
            onARFrameCallback = null;
            assert_true(ranAtLeastOnce);
            done();
          }
        };
      }

      function stepConfirmCameraTextureIsNotNull() {
        const webglCanvas = document.getElementById('webgl-canvas');
        const gl = webglCanvas.getContext('webgl', {
                xrCompatible: true
            });
        const sessionInfo = sessionInfos[sessionTypes.AR];
        const referenceSpace = sessionInfo.currentRefSpace;
        const glBinding = new XRWebGLBinding(sessionInfo.currentSession, gl);

        onARFrameCallback = (session, frame) => {
          const pose = frame.getViewerPose(referenceSpace);
          if (pose) {
            numPosesFound++;

            for (let view of pose.views) {
              if (view.camera) {
                cameraImageTexture = glBinding.getCameraImage(view.camera);
                assert_not_equals(cameraImageTexture, null, "XRWebGLBinding.getCameraImage(...) returned null texture.");
              }
            }
          }

          if (numPosesFound > MAX_NUM_FRAMES_WITH_POSES) {
            onARFrameCallback = null;
            done();
          }
        };
      }

      const countZeros = (array) => {
        let count = 0;
        array.forEach(v => {
          if(v == 0) {
            count++;
          }
        });
        return count;
      }

      // TODO(https://www.crbug.com/1115167): Enable test once pixel reads are working as intended.
      function stepCheckCameraTextureLifetimeLimitedToOneFrame() {
        const webglCanvas = document.getElementById('webgl-canvas');
        const gl = webglCanvas.getContext('webgl', {
          xrCompatible: true
        });
        const sessionInfo = sessionInfos[sessionTypes.AR];
        const referenceSpace = sessionInfo.currentRefSpace;
        const glBinding = new XRWebGLBinding(sessionInfo.currentSession, gl);

        const fb = gl.createFramebuffer();
        const attachmentPoint = gl.COLOR_ATTACHMENT0;
        const level = 0;


        // Assign pixels array non-zero values.
        pixels = new Uint8Array(gl.drawingBufferWidth * gl.drawingBufferHeight * 4);
        pixels.fill(0);

        // Counter of rAFcb calls in which readPixels() call was performed:
        let numIterations = 0;

        onARFrameCallback = (session, frame) => {
          const pose = frame.getViewerPose(referenceSpace);
          if (pose) {
            numPosesFound++;

            // Wait several frames to allow the image buffer to be populated by a camera
            // texture before attempting to get the camera image texture.
            if (numPosesFound >= MIN_NUM_FRAMES_WITH_POSES
             && numPosesFound < MAX_NUM_FRAMES_WITH_POSES) {

              const view = pose.views.find(v => v.camera != null);
              if (view == undefined) return;

              cameraImageTexture = glBinding.getCameraImage(view.camera);

              gl.bindTexture(gl.TEXTURE_2D, cameraImageTexture);
              gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
              gl.framebufferTexture2D(gl.FRAMEBUFFER, attachmentPoint, gl.TEXTURE_2D,
                                      cameraImageTexture, level);

              const fbComplete = readCameraImageTexturePixels(pixels);

              console.log("Framebuffer complete? " + fbComplete);
              if (!fbComplete) return;

              numIterations++;

              const numZeroedRGBAValues = countZeros(pixels);

              // We should get at least some non-zero pixels.
              assert_not_equals(numZeroedRGBAValues, pixels.length,
                                "Camera image texture was empty.");

              // Clean up after ourselves:
              pixels.fill(0);
            }

            if (numPosesFound >= MAX_NUM_FRAMES_WITH_POSES) {
              onARFrameCallback = null;

              assert_not_equals(numIterations, 0, "We should have read the pixels at least once!");

              // Let's try to use stale texture (cameraImageTexture is still referring
              // to a texture obtained in a previous loop):
              gl.bindTexture(gl.TEXTURE_2D, cameraImageTexture);
              gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
              gl.framebufferTexture2D(gl.FRAMEBUFFER, attachmentPoint, gl.TEXTURE_2D,
                                      cameraImageTexture, level);

              const fbComplete = readCameraImageTexturePixels(pixels);
              if (fbComplete) {
                const numZeroedRGBAValues = countZeros(pixels);
                assert_equals(numZeroedRGBAValues, pixels.length,
                              "Camera image should be empty.");
              }

              done();
            }
          }
        };
      }

      // Reads pixels from the framebuffer into the |pixels| parameter.
      // Returns true if framebuffer status was FRAMEBUFFER_COMPLETE, false
      // otherwise. If the framebuffer was incomplete, readPixels() call was not
      // performed.
      function readCameraImageTexturePixels (pixels) {
        if(gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE){
          gl.readPixels(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
          assert_equals(gl.getError(), gl.NO_ERROR);

          return true;
        }

        return false;
      }
    </script>
  </body>
</html>